import os
import threading
import time
import queue
from tools import MessageTool as MT


class User:
    def __init__(self, sock, addr, password_dic, homedir, csvWriteEvent, dicWriteLock, fileWriteLock, msgTransQueue,
                 userNumber):
        '''
        :param sock: 连接套接字句柄
        :param addr: 地址
        :param password_dic: 用户密码字典
        :param homedir: 文件存放位置
        :param csvWriteEvent: 用户密码信息保存事件
        :param dicWriteLock: 字典修改锁
        :param msgTransQueue: 消息转发队列
        '''
        self.sock = sock
        self.addr = addr
        self.homedir = homedir
        self.password_dic = password_dic
        self.csvWriteEvent = csvWriteEvent
        self.dicWriteLock = dicWriteLock
        # self.ID = ID
        # 最少整数
        self.minnum = 2
        # 最多整数
        self.maxnum = 10
        self.sendsize = 1024  # 一次传多少字节文件
        self.msgQueue = queue.Queue()  # 消息队列
        self.fileList = []  # 文件列表
        self.readerDic = {}  # 下载服务读文件句柄
        self.writerDic = {}  # 上传服务写文件句柄
        self.transing = 0  # 正在传输的数目
        self.msgTransQueue = msgTransQueue  # 消息发送队列，中转消息用
        self.userNumber = userNumber  # 用户序号
        self.fileWriteLock = fileWriteLock  # 防止用多个用户同时写入同一文件，创建文件
        self.homedir = homedir
        self.dirloc = ''  # 当前位置
        self.online = True
        self.offlineList = []  # 离线文件列表

    def close(self):
        self.sock.close()
        print('断开了与用户', self.ID, '的连接')
        msg = self.packing(MT.S_USER_LIST, MT.DELETE, self.userNumber, message=self.ID.encode('utf-8'))
        self.msgTransQueue.put((-1, self.userNumber, msg))
        self.online = False

    def getMessage(self):
        '''
        接收并解装消息
        :return: 返回解封完毕的消息
        '''
        return MT.recv(self.sock,2)
    def login(self):
        # 登录操作
        while True:
            ms = self.getMessage()
            if not ms:
                # 登录失败
                print("登录失败")
                self.sock.close()
                self.msgTransQueue.put((-3, self.userNumber))
                return
            if ms.function == MT.C_LOGIN:
                self.ID = ms.message.decode('utf-8')
                if self.ID not in self.password_dic:
                    # 不在字典中认为用户没有注册，为用户注册新账号，密码为12345
                    # 加锁，防止在写入过程中对字典进行修改
                    self.dicWriteLock.acquire()
                    self.password_dic[self.ID] = 12345
                    self.dicWriteLock.release()
                    # 通过事件令主线程写入
                    self.csvWriteEvent.set()
                    # 为新用户新建文件夹
                    os.makedirs(os.path.join(self.homedir, self.ID))
                # 开始质询
                ints = MT.getRandomInts(self.minnum, self.maxnum)
                self.send(MT.S_LOGIN, status=MT.SUCCESS, message=MT.int2bytes(4, *ints))
            else:
                self.send(MT.S_LOGIN, MT.FAILED, message='请先登录'.encode('utf-8'))
                continue

            # 获取质询响应结果
            ms = self.getMessage()
            if not ms:
                # 登录失败
                self.sock.close()
                self.msgTransQueue.put((-3, self.userNumber))
                return
            if ms.function != MT.C_QUEST_RESULT:
                self.send(MT.S_QUEST_RESULT, MT.FAILED, message='请回复质询结果'.encode('utf-8'))
                continue
            if next(MT.bytes2int(4, ms.message)) == MT.chap(ints, self.password_dic[self.ID]):
                self.send(MT.S_QUEST_RESULT, MT.SUCCESS)
                break
            else:
                self.send(MT.S_QUEST_RESULT, MT.FAILED, message='结果错误'.encode('utf-8'))
        print('用户%s登录成功！' % self.ID)
        # 向所有用户广播新用户的消息
        self.msgTransQueue.put(
            (-2, self.userNumber,
             self.packing(MT.S_USER_LIST, MT.APPEND, self.userNumber, message=self.ID.encode('utf-8'))))
        threading.Thread(target=self.listen).start()
        threading.Thread(target=self.msgQueueSend).start()

    def listen(self):
        # 帧听操作
        while True:
            ms = self.getMessage()
            if not ms:
                self.close()
                return
                # 修改密码
            if ms.function == MT.C_PASSWORD:
                password = (next(MT.bytes2int(4, ms.message)))
                if password >= 2 ** (4 * 8):
                    self.send(MT.S_PASSWORD, MT.FAILED, message='密码值过大'.encode('utf-8'))
                else:
                    self.dicWriteLock.acquire()
                    self.password_dic[self.ID] = password
                    self.dicWriteLock.release()
                    # 通过事件令主线程写入
                    self.csvWriteEvent.set()
                    self.send(MT.S_PASSWORD, MT.SUCCESS)
            # 申请文件列表
            elif ms.function == MT.C_LIST:
                if self.transing > 0:
                    self.send(MT.S_LIST, MT.FAILED, message='还有文件正在传输，请等待文件传输完成'.encode('utf-8'))
                else:
                    if ms.status == MT.PUBLIC:
                        # 公共文件夹
                        self.dirloc = os.path.join(self.homedir, 'public')
                    elif ms.status == MT.PRIVATE:
                        # 私人文件夹以个人ID命名
                        self.dirloc = os.path.join(self.homedir, self.ID)
                    else:
                        self.send(MT.S_LIST, MT.FAILED, message='未知的申请类型'.encode('utf-8'))
                        continue
                    self.send(MT.S_LIST, MT.SUCCESS)
                    if not os.path.exists(self.dirloc):
                        os.makedirs(self.dirloc)
                    self.fileList = os.listdir(self.dirloc)
                    time.sleep(0.1)
                    for i, name in enumerate(self.fileList):
                        self.send(MT.S_LIST_TRANS, MT.MORE_TEAM, filenum=i, message=name.encode('utf-8'))
                    self.send(MT.S_LIST_TRANS, MT.END)
            # 申请文件下载
            elif ms.function == MT.C_DOWN:
                if ms.fileNumber >= len(self.fileList):
                    self.send(MT.S_DOWN, MT.REFUSE, message='文件不存在'.encode('utf-8'))
                elif not os.path.getsize(os.path.join(self.dirloc, self.fileList[ms.fileNumber])):
                    self.send(MT.S_DOWN, MT.REFUSE, message='文件为空'.encode('utf-8'))
                else:
                    try:
                        fileReader = open(os.path.join(self.dirloc, self.fileList[ms.fileNumber]), 'rb')
                    except:
                        self.send(MT.S_DOWN, MT.REFUSE, message='打开文件失败'.encode('utf-8'))
                    else:
                        print('开始发送文件: ', self.fileList[ms.fileNumber])
                        self.send(MT.S_DOWN, MT.ALLOW, filenum=ms.fileNumber)
                        self.transing += 1
                        threading.Thread(target=self.fileTrans, args=(fileReader, ms.fileNumber)).start()

            # 申请文件上传
            elif ms.function == MT.C_UP:
                name = ms.message.decode('utf-8')
                self.fileWriteLock.acquire()
                if name in self.fileList:
                    self.send(MT.S_UP, MT.REFUSE, message=name.encode('utf-8'))
                else:
                    num = len(self.fileList)  # 新的文件编号
                    self.fileList.append(name)
                    # 新建一个writer
                    self.writerDic[num] = open(os.path.join(self.dirloc, name), 'wb')
                    self.transing += 1
                    self.send(MT.S_UP, MT.ALLOW, filenum=len(self.fileList) - 1, message=name.encode('utf-8'))
                self.fileWriteLock.release()
            # 上传文件传输
            elif ms.function == MT.C_UP_TRANS:
                if ms.status == MT.END:  # 传输结束
                    self.writerDic.pop(ms.fileNumber).close()
                    self.transing -= 1
                elif ms.status == MT.MORE_TEAM:
                    self.writerDic[ms.fileNumber].write(ms.message)
            elif ms.function in [MT.T_USER_FILE_REQ, MT.T_USER_FILE_RES, MT.T_USER_FILE_TRANS, MT.T_USER_MESSAGE]:
                # 用户间消息，稍作修改转发
                aimNumber = ms.userNumber
                if aimNumber < 0 or aimNumber > 255:
                    continue
                ms.userNumber = self.userNumber
                self.msgTransQueue.put((aimNumber, MT.packingMs(ms)))
            elif ms.function == MT.C_OFFLINE:  # 离线文件传输请求
                msgs = ms.message.decode('utf-8').split(maxsplit=1)
                aimUser = msgs[0]
                fileName = msgs[1]
                fileNum = 0
                status = MT.REFUSE
                if os.path.exists(os.path.join(self.homedir, aimUser)):  # 用户存在
                    if not os.path.exists(os.path.join(self.homedir, aimUser, fileName)):  # 文件不存在
                        writer = open(os.path.join(self.homedir, aimUser, fileName), 'wb')
                        for i, w in enumerate(self.offlineList):
                            if w == -1:  # 查找已有序号是否有可分配的
                                self.offlineList[i] = writer
                                fileNum = i
                                status = MT.ALLOW
                                break
                        else:  # 否则额外分配一个
                            if len(self.offlineList) < 256:  # 序号不超过255
                                fileNum = len(self.offlineList)
                                self.offlineList.append(writer)
                                status = MT.ALLOW
                self.send(MT.C_OFFLINE, status, filenum=fileNum, message=ms.message)
            elif ms.function == MT.C_OFFLINE_TRANS:  # 离线文件传输
                if ms.status == MT.MORE_TEAM:
                    try:
                        self.offlineList[ms.fileNumber].write(ms.message)
                    except BaseException as e:
                        print(e)
                elif ms.status == MT.END:
                    try:
                        self.offlineList[ms.fileNumber].close()
                        self.offlineList[ms.fileNumber] = -1
                    except BaseException as e:
                        print(e)

    def msgQueueSend(self):
        '''
        发送消息队列中的消息
        :return:
        '''
        while True:
            try:
                msg = self.msgQueue.get(timeout=5)  # 防止套接字关闭后线程死锁
                if not self.sendData(msg):
                    return
            except:
                if not self.online:
                    return

    def fileTrans(self, fileReader, fileNumber):
        '''
        向指定用户或客户端发送文件
        :param file: 要发送的文件
        :param fileNumber: 文件编号
        :return:
        '''
        while True:
            chunk = fileReader.read(self.sendsize)
            if not chunk:
                self.transing -= 1
                if not self.send(MT.S_DOWN_TRANS, MT.END, filenum=fileNumber):
                    return
                break
            else:
                if not self.send(MT.S_DOWN_TRANS, MT.MORE_TEAM, filenum=fileNumber, message=chunk):
                    return

    @staticmethod
    def packing(function, status=0, userNum=0, filenum=0, message=bytes([0])):
        '''
        消息封装
        :param function: 功能字，一字节整数
        :param status: 状态字，一字节整数
        :param filenum: 文件编号，一字节整数
        :param teamnum: 分组编号，一字节整数
        :param message: 消息内容，bytes
        :return: 封装好的消息
        '''
        msg = MT.packing(MT.int2bytes(1, function,status, userNum,  filenum), message)
        return msg

    def send(self, function, status=0, userNum=0, filenum=0, message=bytes([0])):
        '''
        发送操作
        :return 发送状态
        '''
        
        data = self.packing(function, status, userNum, filenum, message)
        # print(data)
        return self.sendData(data)

    def sendData(self, data):
        return MT.send(self.sock,data)
